Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Refactor Plot type & road to 1.0 #1085

Closed
wants to merge 14 commits into from
Closed

Conversation

SimonDanisch
Copy link
Member

@SimonDanisch SimonDanisch commented Jun 30, 2021

This is a pretty big rewrite of the internal plot type.
The goal to not be breaking, though! This is a stretch goal, and quite a few things will likely be slightly breaking. One should think of this PR as Makie's 0.7 Julia version, on it's way to a breaking 1.0 release. This refactor will introduce warnings for features we want to deprecate, and will lay the groundwork to do the actual breaking changes.

The planned changes:

  • each atomic plot type now has a fixed set of fields with fixed types. This enables:

    • good error messages for wrong plot attributes (still need to add that bit in the current implementation)
    • compilation time improvements (!?)
    • types become self documenting
    • better API for backends. Right now, it's pretty fuzzy to see what the basic plot types support, leading to bugs + making it more difficult to write new backends/maintain them
    • merging themes can be a clean operation. Right now it's a big mess where it happens, how and when. With the new plot type, all fields not set by a user are put into a set of uninitilized_fields, and then get updated from the theme when actually drawn
  • make it possible to construct a plot type without a scene. This enables

    • constructing plots, taking their boundingbox etc, without plotting anything
    • easier and cleaner adding & removing of plots from scenes. Right now it's possible, but unclean since the plot type still keeps some connections to the original parent
    • reactive API: one can build whole scene graphs of plots without the need of a parent scene, and can construct them cheaply in an animation loop, without the whole overhead of building the whole scene. This also makes diffing of scene graphs easier (also because of the above)
  • revamp of observables inside plot types

    • plot types are now mutable with concrete values as fields. Any setfield! operation will immediately converted the values to the strict field types
    • remove the Attributes type, which was storing any plot attribute as Observable{Any}
    • each plot object has an on_event observable, which one can use to register to setfield! events, or other events like mouse/camera/axis-limit-changes
    • non observable workflows become first class, since the creation of a plot type without any observable doesn't create any new observables, while one can still subscribe to attributes updating via setfield!
    • no duplicate convert_attribute calls in multiple parts of the pipelines. In the old infrastructure, everything got converted lazily, so there wasn't a clear point to convert.. Some some recipe did already apply conversions, and the backend then applied conversions again
    • enables updating multiple attributes together, fixing the age old problems with synchronous updates. Related API suggestion for avoiding synchronous update issues #798
  • conversion pipeline in plot constructor

    • clear point where the conversion happens
    • Enables to have e.g. Scatter(1:4) to create the correct plot object, without a scene present
    • makes it possible to create plot instances very early, making it easier in the rest of the pipeline to e.g. calculate boundingboxes earlier

Timeline:

  • create plot type + macro to create them
  • implement observable free updates
  • new attribute conversion pipeline
  • implement alpha attribute
  • throw great error messages on unsupported attributes
  • clean up & document attributes part of every plot object (label, participate_in_limits, anti-aliasing, color, alpha, ..and more?)
  • start replacing basic plot types
    • Scatter (GLMakie working with basic attributes)
    • Lines (GLMakie works)
    • LineSegments (GLMakie works)
    • Mesh
    • Surface
    • Volume
    • Heatmap
    • Image
  • figure out recipe pipeline with new conversion infrastructure (started)
  • Fix all regressions as gracefully as possible

@SimonDanisch SimonDanisch changed the title [WIP] Refactor Plot type [WIP] Refactor Plot type & road to 1.0 Jul 15, 2021
Copy link
Contributor

@timholy timholy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a preliminary review focusing just on your struct types. I am not sure I'll be able to provide much more useful feedback at this stage, but hopefully this helps at least somewhat.

Anything user-facing might need to stay generic, but if you're expecting a fair amount of transformation anyway (e.g., in terms of Float32 argtypes or something), then you might as well transform the container in addition to the eltype. For reference, here are the rules:

julia> isconcretetype(Vector{Float32})
true

julia> isconcretetype(AbstractVector{Float32})
false

julia> isconcretetype(Vector{Union{Nothing,Float32}})
true

julia> isconcretetype(Vector{Any})  # *elements* are not concretely-typed, but the *container* is
true

julia> isconcretetype(Vector{<:Real})
false

Declaring something like

struct Scatter{N,A<:AbstractVector}
    somefield::A
    ...

is of course fine from the standpoint of inference of somefield (it will be concrete if the concrete Scatter is known), but problematic from the standpoint of more type-diversity among Scatter concrete types and thus longer latency.

There are cases where your AbstractVector field is the correct solution, but you should make sure you're very clear on why you're doing it that way. Basically that comes down to circumstances where you deliberately want to "hide the type" as a strategy to reduce latency. Under such circumstances you'd have carefully designed function-barriers that trigger runtime specialization on the specific AbstractVector. When deliberate (and in conjunction with some carefully-placed @nospecialize annotations), this can be an effective and powerful strategy, but it's not something you should do by accident.

@@ -0,0 +1,43 @@
@plottype mutable struct Lines{N} <: AbstractPlot{Any}
position::AbstractVector{Point{N,Float32}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be an AbstractVector? There are sometimes reason to choose that but I'm sure you know it will be non-inferrable. Since you're specifying the eltype so tightly, it seems likely this won't be user-facing anyway, and so can you just make this Vector?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's up for debate... I would really like to keep it an AbstractVector, since there are quite a few use cases like efficiently mapping a view to a gpu buffer, or passing through GPUArrays and things like that...
But I guess we should benchmark the impact, and if it's huge, maybe try to stick to Vector as much as possible...

Copy link

@diegozea diegozea Aug 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help to make the type of positions field a type-parameter of Lines?

Copy link
Contributor

@timholy timholy Aug 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can keep it AbstractVector. The trick is either (1) to not pull it out of the Lines struct until the "last minute" and/or (2) annotate dispatches @nospecialize(position::AbstractVector{...}) until you deliberately want a function barrier (basically, for any method with a loop over the elements of position).

But the N type parameter will break @nospecialize, i.e., you can't expect foo(@nospecialize(position::AbstractVector{Point{N,Float32}})) where N to not fully specialize on position.

If your diversity of position types is not large, then this may not be a big problem. It sounds like you're going to become a @nospecialize expert soon 🙂 .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help to make the type of positions field a type-parameter of Lines?

It would improve inferrability at the cost of likely greater latency, because the compiler would have to specialize every call for every Scatter type you use.

@@ -0,0 +1,36 @@
const MarkerSizeTypes{N} = TorVector{Union{Float32, Vec{N, Float32}}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's remotely possible that it will be better to "flatten" the Union here, but let's wait and see how the inference works out (it's identical in consequence, so that's trivial to change later).

@@ -0,0 +1,36 @@
const MarkerSizeTypes{N} = TorVector{Union{Float32, Vec{N, Float32}}}
const MarkerTypes = TorVector{<:Union{Symbol,Char, Type{Circle}, Type{Rect2D}}}
const DistanceFieldType = Union{Nothing, AbstractMatrix{<: Union{Colorant, Number}}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more concrete you can make this, the better for inference & latency. But anything user-facing probably has to be general. You could always use

const DistanceFieldType = Union{Nothing, Matrix{<: Union{Colorant, Number}}}

for struct fields and

const DistanceFieldTypeLike = Union{Nothing, <:AbstractMatrix{<: Union{Colorant, Number}}}

for dispatch to indicate "something that could obviously be converted to a DistanceFieldType.

@@ -68,6 +64,12 @@ end

const px = Pixel(1)

const Space = Type{<: Unit}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be rough for some of your previous field types...if it's possible to standardize internally...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this was just a stop gap solution, to not trigger a bigger refactor... I think this can just be an enum going forward..

font::NativeFont = "Dejavue Sans"
strokecolor::ColorType = (:black, 0.0)
strokewidth::TorVector{Float32} = 0
align::Vec2f0 = (:left, :bottom)
Copy link
Contributor

@timholy timholy Aug 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, how does this work? I can't imagine a method that doesn't involve type-piracy. Oh, unless the @plottype handles it.

abstract type AbstractScreen <: AbstractDisplay end

const SceneLike = Union{AbstractScene, ScenePlot}
const TorVector{T} = Union{T, AbstractVector{T}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is used in struct fields, it sure would be nice to make this Vector{T} instead of AbstractVector if possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaaah... 😢 I planned to Benchmark this when the PR is in a better state - but would be sad to give up on AbstractArray support, but maybe we can make it work one way or the other...

Copy link
Contributor

@timholy timholy Aug 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can of course have both TorAbstractVector and TorVector. If nothing else you might want to change the name to acknowledge the abstraction. (TorAVector would be pretty short if that's an issue.)

@SimonDanisch
Copy link
Member Author

Will close this for now... Will bring back the important changes by cherry picking in much smaller PRs!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants